//
// Created by guoshunw on 24-2-27.
//

#ifndef AS_HOUYI_MESSAGEFIELDSWITHTYPE_H
#define AS_HOUYI_MESSAGEFIELDSWITHTYPE_H

#include <memory>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

#include "enum.h"
#include "log.h"

namespace davinci::edr {

    BETTER_ENUM(Type, int, CPPTYPE_DOUBLE = 5, CPPTYPE_STRING = 9,
                CPPTYPE_DOUBLE_VEC = 10, MAX_CPPTYPE = 100)

    template<typename T, typename... Types>
    struct is_one_of : std::disjunction<std::is_same<T, Types>...> {};

    class FieldWithType {
        using var_t = std::variant<double, std::string, std::vector<double>>;

    public:
        explicit FieldWithType(var_t value, Type type = Type::CPPTYPE_DOUBLE)
            : type_(type), value_(std::move(value)) {}

        [[nodiscard]] Type getType() const { return type_; }

        template<typename T, std::enable_if_t<
                                     is_one_of<T, double, std::string, std::vector<double>>::value, int> = 0>
        [[nodiscard]] T as() const {
            return std::get<T>(value_);
        }

        [[nodiscard]] double asDouble() const {
            if (type_ != +Type::CPPTYPE_DOUBLE) {
                LOGW << "wrong type: " << type_;
                return 0.0;
            }
            return as<double>();
        }

        [[nodiscard]] std::string asString() const {
            if (type_ != +Type::CPPTYPE_STRING) {
                LOGW << "wrong type: " << type_;
                return "";
            }
            return as<std::string>();
        }

        [[nodiscard]] std::vector<double> asDoubleVec() const {
            if (type_ != +Type::CPPTYPE_DOUBLE_VEC) {
                LOGW << "wrong type: " << type_;
                return {};
            }
            return as<std::vector<double>>();
        }

        [[nodiscard]] std::string toString() const;
        [[nodiscard]] inline std::string toDebugString() const {
            return "type: " + std::to_string(type_) + ", value: " + toString();
        }

        friend std::ostream &operator<<(std::ostream &os,
                                        const FieldWithType &field) {
            return os << field.toString();
        }

    private:
        Type type_;
        std::variant<double, std::string, std::vector<double>> value_{};
    };

    class MessageFieldsWithType {
    public:
        void addValue(const std::string &key, double value) {
            fields_.emplace(key, FieldWithType{value, Type::CPPTYPE_DOUBLE});
        }

        void addValue(const std::string &key, std::string value) {
            fields_.emplace(key, FieldWithType{value, Type::CPPTYPE_STRING});
        }

        void addValue(const std::string &key, const char *value) {
            this->addValue(key, std::string(value));
        }

        void addValue(const std::string &key, std::vector<double> value) {
            fields_.emplace(key, FieldWithType{value, Type::CPPTYPE_DOUBLE_VEC});
        }

        [[nodiscard]] FieldWithType getField(
                const std::string &key, FieldWithType default_value = FieldWithType{
                                                0.0, Type::MAX_CPPTYPE}) const;

        [[nodiscard]] std::string toDebugString(bool isCompact = false) const;

    private:
        std::unordered_map<std::string, FieldWithType> fields_;
    };

}// namespace davinci::edr

#endif// AS_HOUYI_MESSAGEFIELDSWITHTYPE_H
